﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Cfg;
using NHibernate.Audit.Cfg;
using NHibernate.Mapping;
using NHibernate.Audit.Mapping;

namespace NHibernate.Audit.Mapping
{
    public class MetadataBuilder
    {

        public MetadataBuilder(Configuration Configuration, AuditConfiguration AuditConfiguration)
        {
            var ClassSource = new ConfigurationPersistentClassSource(Configuration);
            Setup(ClassSource, AuditConfiguration);
        }

        public MetadataBuilder(IPersistentClassSource ClassSource, AuditConfiguration AuditConfiguration)
        {
            Setup(ClassSource, AuditConfiguration);
        }

        private void Setup(IPersistentClassSource ClassSource, AuditConfiguration AuditConfiguration)
        {
            classSource = ClassSource;
            auditConfiguration = AuditConfiguration;
            classMetadataCollection = new HashSet<AuditClassMetadata>();
        }

        protected IPersistentClassSource classSource;
        protected AuditConfiguration auditConfiguration;

        private ICollection<AuditClassMetadata> classMetadataCollection;

        public ICollection<AuditClassMetadata> ClassMetadataCollection
        {
            get { return classMetadataCollection; }
        }

        public void BuildMetadata()
        {
            var convention = auditConfiguration.ClassConvention;
            var auditableClasses = (from c in classSource.Classes 
                                    where !IsRevisionInfo(c) && convention.AuditClass(c)
                                    select c);
            foreach (var clazz in auditableClasses)
            {
                var tableMetadata = BuildAuditTableMetadata(clazz.Table);
                var classMetadata = BuildAuditClassMetadata(clazz, tableMetadata);
                classMetadataCollection.Add(classMetadata);
            }
        }

        protected bool IsRevisionInfo(PersistentClass clazz)
        {
            var revisionClass = auditConfiguration.RevisionConfiguration.GetRevisionClass();
            var revisionClassName = revisionClass.AssemblyQualifiedName;
            return string.Compare(clazz.ClassName, revisionClassName, true) == 0;
        }

        protected virtual AuditTableMetadata BuildAuditTableMetadata(Table table)
        {
            var convention = auditConfiguration.TableConvention;
            var revisionConfig = auditConfiguration.RevisionConfiguration;
            var revisionPropType = revisionConfig.GetRevisionIdentityPropertyType();
            var val = new SimpleValue()
            {
                TypeName = NHibernateUtil.GuessType(revisionPropType).Name,
            };

            var revisionCol = new Column()
            {
                Name = revisionConfig.GetRevisionColumnName(table),
                Value = val,
                IsNullable = false
            };
            val.AddColumn(revisionCol);
                        
            var meta = new AuditTableMetadata()
            {
                Name = convention.GetAuditTableNameFor(table),
                Schema = convention.GetAuditTableSchemaFor(table),
                Catalog = convention.GetAuditTableCatalogFor(table),
                RevisionColumn = revisionCol
            };


            return meta;
        }

        protected virtual AuditClassMetadata BuildAuditClassMetadata(PersistentClass realClass, AuditTableMetadata tableMetadata)
        {
            var convention = auditConfiguration.ClassConvention;
            var meta = new AuditClassMetadata(realClass, tableMetadata)
            {
                 AuditEntityName = convention.GetAuditEntityNameFor(realClass),
                 AuditClassName = convention.GetAuditClassNameFor(realClass)
            };

            var auditableProperties = (from p in realClass.PropertyIterator
                                       where convention.AuditProperty(p)
                                       select p);

            foreach (var prop in auditableProperties)
                BuildAuditPropertyMetadata(prop, meta);

            meta.RevisionProperty = BuildRevisionProperty(tableMetadata, realClass);

            return meta;
        }

        protected virtual AuditPropertyMetadata BuildAuditPropertyMetadata(Property property, AuditClassMetadata auditClassMetadata)
        {
            var meta =  new AuditPropertyMetadata(property)
            {
                 ClassMetadata = auditClassMetadata 
            };
            auditClassMetadata.AddProperty(meta);
            return meta;
        }

        protected virtual Property BuildRevisionProperty(AuditTableMetadata tableMetadata, PersistentClass realClass)
        {
            var revisionConfig = auditConfiguration.RevisionConfiguration;
            var val = tableMetadata.RevisionColumn.Value;
            var prop = new Property(val);
            prop.Name = revisionConfig.GetRevisionPropertyName(realClass);
            return prop;
        }

    }
}
